// ----------------------------------------
// Filename: TIMER.c
// Description: The code to operate the PIC's timers
// Author: Jon Maloney
// Date: 1-May-2012
//

//************************************************************
//                    Include .h files
//************************************************************
#include "Timer.h"
#include "microRTOS.h"
//************************************************************
//                    Include external references
//************************************************************

//************************************************************
//                    Define Statements
//************************************************************

//Defines for the DateTime structure
#define _Second     s.second
#define _Minute     s.minute
#define _Hour       s.hour
#define _Month      s.month
#define _Day        s.day
#define _Year       s.year

//************************************************************
//                    Struct, Union, enum Statements
//************************************************************

typedef union 
{
  UINT32  DateTimeLong;
  struct 
  {
    unsigned year   :12;     //Years (if using structure as RTC years from 2000AD)
    unsigned month  :4;     //the month i.e. 2 = feburary
    unsigned day    :5;     //Day of month i.e 1st of march = 1
    unsigned hour   :5;     //Hours in the day 24 hour format i.e. 3am = 3, 3pm = 15
    unsigned minute :6;     //Minuetes in an hour
  } s;      
} DateTime;

//************************************************************
//                    Global variables and constants
//************************************************************
vFunc_ptr timerCallBackFunction;
T_TIMER_RATE timeoutRate;
UINT8 frequencyMultiple[3] = {8,8,4};

static UINT8 monthDays[13] = {0,  //Unused so our monthDays indexing begins with 1
                              31, //January
                              28, //Feburary
                              31, //March
                              30, //April
                              31, //May
                              30, //June
                              31, //July
                              31, //August
                              30, //September
                              31, //October
                              30, //November
                              31};//December

//************************************************************
//                    Prototype Private Functions
//************************************************************

void timer_KernelTimer(void * functionArgument);
void zeroDateTime(DateTime * RunningTime);
void setAlarmTime(DateTime * alarmTime, T_TIMER_RATE t_Rate);

//************************************************************
//                    Implementation
//************************************************************



void __attribute__((__interrupt__, auto_psv)) _T3Interrupt( void )
{
#if _DEBUG == 1
	
	taskENTER_CRITICAL();
	{
		printf( "Timer 3 Interrupt\n" );
		fflush(stdout);
	}
	taskEXIT_CRITICAL();


	if(AD1CON1bits.ASAM == 1)
	{//An error has occured.
		printf( "ADC Sampling error:- Timer interrupted before sampling completed.");
	}
#endif
	
	timerCallBackFunction();

	/* Clear the timer interrupt. */
	_T3IF = 0;
}

/*
 * Setup a timer for a regular tick.
 */
void Timer_SetupTickTimerInterrupt( void )
{
	const unsigned long ulCompareMatch = ( ( configCPU_CLOCK_HZ / configTICK_TIMER_PRESCALE ) / configTICK_RATE_HZ ) - 1;

	/* Prescale of 1. */
	T1CON = 0;
	TMR1 = 0;

	/* Setup the prescale value. */
	T1CONbits.TCKPS0 = 0;
	T1CONbits.TCKPS1 = 0;

	PR1 = ( unsigned short ) ulCompareMatch;

	/* Setup timer 1 interrupt priority. */
	IPC0bits.T1IP = configKERNEL_INTERRUPT_PRIORITY;

	/* Clear the interrupt as a starting condition. */
	IFS0bits.T1IF = 0;

	/* Enable the interrupt. */
	IEC0bits.T1IE = 1;


	
	/* Start the timer. */
	T1CONbits.TON = 1;
}

UINT16 Timer_SetupTimer3( T_TIMER_RATE * t_Rate, void *callOnTimeOut )
{

	static BOOL isInit = pdFALSE;
	static TCB * timerThread = NULL;
	float frequency = 0;
	UINT8 x = 0;
	
	//Stop the timer whilst it is configured.
	T3CONbits.TON = 0;

	if(t_Rate->_FREQUENCY == 0)
		return 0;

	timeoutRate = *t_Rate;	//Set the rate at which the timer will interrupt

	//Setup the function that will be called when the timer interrupts
	timerCallBackFunction = callOnTimeOut;
	
	//If the frequency is > 1Hz we will use a hardware timer
	// 	If the frequency is < 1Hz we will use a kernel timer
	if(timeoutRate._UNITS >= _UNIT_MILLI_SECONDS)
	{	//We will use a kernel timer
		
		//Is there an existing timer thread?
		if(timerThread !=NULL)
			Thread_Delete(timerThread);//Delete it
			
		//Setup a thread that will timeout at the specified frequency
		timerThread = Thread_Create(timer_KernelTimer, NULL, 200, configKERNEL_INTERRUPT_PRIORITY);

		return 1;
	}

	if(isInit == pdFALSE)
	{
		/* T3 is used to generate interrupts.*/
		TMR3 = 0;
		
		T3CONbits.TCS 		= 0;	//Timerx Clock Source Select bit
									//		1 = External clock from pin, TxCK (on the rising edge)
									//		0 = Internal clock (FOSC/2)
		T3CONbits.TCKPS		= 0;	//TimerX Input Clock Prescale Select bits	
									//		11 = 1:256
									//		10 = 1:64
									//		01 = 1:8
									//		00 = 1:1
		T3CONbits.TGATE		= 0;	//TimerY Gated Time Accumulation Enable bit
									//		1 = Gated time accumulation enabled
									//		0 = Gated time accumulation disabled
		T3CONbits.TSIDL		= 0;	//Stop in Idle Mode bit
									//		1 = Discontinue module operation when device enters Idle mode
									//		0 = Continue module operation in Idle mode	
		isInit = pdTRUE;
	}

	if(timeoutRate._UNITS == _UNIT_MICRO_SECONDS)		//Frequency is in KHz Range
	{
		T3CONbits.TCKPS = 0;
		frequency = 1000000 / (float)timeoutRate._PERIOD;
	}
	else if( timeoutRate._UNITS == _UNIT_MILLI_SECONDS)//Frequency is in Hz Range
	{
		T3CONbits.TCKPS = 0;
		frequency = 1000 / (float)timeoutRate._PERIOD;
		
		for(x = 0; x<3; x++)
		{
			if(frequency < 245)
				break;
			
			T3CONbits.TCKPS++;
			frequency = frequency * frequencyMultiple[x];
		}
	}
	
	/* Timer is going to interrupt at Frequency. */
	PR3 = ( UINT16 ) ( configCPU_CLOCK_HZ / ( ( UINT32 ) frequency + 0.5 ) );	//0.5 rounds the conversion

	/* Setups timer 3 interrupt priority to be above the kernel priority so 
	the timer jitter is not effected by the kernel activity. */
	_T3IP = ( UINT8 )configKERNEL_INTERRUPT_PRIORITY + 1;

	/* Clear the interrupt as a starting condition. */
	_T3IF = 0;

	/* Enable the timer interrupt*/
	_T3IE = 1;
	
	/* Start the timer. */
	T3CONbits.TON = 1;	//Timer On bit
						//		1 = Starts Timer
						//		0 = Stops Timer	

	return 1;
}

void timer_KernelTimer(void * functionArgument)
{		
	DateTime alarmDateTime;
	DateTime RunningTime;
	
	if(timeoutRate._UNITS >= _UNIT_MINUTES)
	{	
		zeroDateTime(&RunningTime);
		setAlarmTime(&alarmDateTime, timeoutRate);
	}
	
	for(;;)
	{
		if(timeoutRate._UNITS == _UNIT_MILLI_SECONDS)
		{
			Thread_Delay((UINT16)timeoutRate._PERIOD);
			timerCallBackFunction();	//Run the function that that needs to be syncronised wiht the timer
		}
		else if(timeoutRate._UNITS == _UNIT_SECONDS)
		{
			Thread_Delay((UINT16)timeoutRate._PERIOD * 1000);
			timerCallBackFunction();	//Run the function that that needs to be syncronised wiht the timer
		}
		else if(timeoutRate._UNITS >= _UNIT_MINUTES)
		{
			Thread_Delay(60000);	//Delay for 1 minute
			RunningTime._Minute++;
			
			//If a timeout has occured
			if(RunningTime.DateTimeLong == alarmDateTime.DateTimeLong)
			{
				timerCallBackFunction();	//Run the function that that needs to be syncronised with the timer
				zeroDateTime(&RunningTime);
			}
			else
			{	
				//Check to see if hours needs to be updated
				if(RunningTime._Minute >= MinutesInHour) 
				{
				  RunningTime._Hour++;
				  RunningTime._Minute -= MinutesInHour;
				  
				  //Check to see if days needs to be updated
				  if(RunningTime._Hour >= HourInDay) 
				  {  
				    RunningTime._Day++;
				    RunningTime._Hour -= HourInDay;
				    
				    //Check to see if months needs to be updated
				    if(RunningTime._Month >= monthDays[RunningTime._Month]) 
				    {
				      RunningTime._Month++;
				      RunningTime._Day -= monthDays[RunningTime._Month];            
				      
				      //Check to see if years needs to be updated
				      if(RunningTime._Month >=MonthInYear) 
				      {  
				        RunningTime._Year++;
				        RunningTime._Month -= MonthInYear;
				        
				        //If it is a leap year modify number of days in feburary
				        if((RunningTime._Year & 0x03) == 0) 
				          monthDays[Feburary] = 29;
				        else
				          monthDays[Feburary] = 28;
				      }
				    } 
				  }
				}
			}
		}	
	}
}


void zeroDateTime(DateTime * RunningTime)
{
	RunningTime->_Year   = 0;    // 2000AD
	RunningTime->_Month  = 1;    //January
	RunningTime->_Day	= 1;	//1st of January
	RunningTime->_Hour	= 0;	//12:00 midnight 24 hour time
	RunningTime->_Minute	= 0;		

	//If it is a leap year modify number of days in feburary
	if((RunningTime->_Year & 0x03) == 0) 
	  monthDays[Feburary] = 29;
	else
	  monthDays[Feburary] = 28;
}

void setAlarmTime(DateTime * alarmTime, T_TIMER_RATE t_Rate)
{
	zeroDateTime(alarmTime);

	if(timeoutRate._UNITS == _UNIT_MINUTES)
	{
		while(t_Rate._PERIOD >= MinutesInHour)
		{
			alarmTime->_Hour++;
			t_Rate._PERIOD -= MinutesInHour;
		}
		alarmTime->_Minute = t_Rate._PERIOD;	//The leftovers will be in minutes
	}
	else if(timeoutRate._UNITS == _UNIT_HOURS)
	{
		while(t_Rate._PERIOD >= HourInDay)
		{
			alarmTime->_Day++;
			t_Rate._PERIOD -= HourInDay;
		}
		alarmTime->_Hour = t_Rate._PERIOD;	//The leftovers will be in hours
	}
	else if(timeoutRate._UNITS == _UNIT_DAY)
	{
		while(t_Rate._PERIOD >= monthDays[alarmTime->_Month])
		{
			t_Rate._PERIOD -= monthDays[alarmTime->_Month];
			alarmTime->_Month++;
			
			if(alarmTime->_Month >=MonthInYear)
			{  
				alarmTime->_Year++;
				alarmTime->_Month -= MonthInYear;
			}
		}
		alarmTime->_Day = t_Rate._PERIOD;	//The leftovers will be in days
	}
	else if(timeoutRate._UNITS == _UNIT_YEAR)
	{
		alarmTime->_Year = t_Rate._PERIOD;
	}
}
